﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a forum topic.
    /// </summary>
    public class CreateForumTopic : CreateAuditable<ForumTopicEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FORUM");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var member = ctx.ForumMember(uow);
                        if(member == null || !member.Flags.HasFlag(ForumMemberFlags.Active) ||
                            member.EntityID != MemberID)
                        {
                            return false;
                        }
                        var forum = uow.Execute(new GetForum
                        {
                            EntityID = ForumID
                        }).Result;
                        if(forum == null)
                            return false;
                        var acl = forum.AccessRules;
                        return !acl.Any() || acl.Any(x => member.Account.Roles.Contains(x.Role) &&
                                x.AccessRights >= ForumAccessRights.ReadWrite);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the member identifier.
        /// </summary>
        public int MemberID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the forum identifier.
        /// </summary>
        public int ForumID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        public string Title
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        public string Description
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public ForumTopicFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrEmpty(Title))
                return Err.Of<ForumTopicEntity>().NotValid("Title");
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out ForumTopicEntity result)
        {
            base.Work(uow, out result);
            uow.Execute(new UpdateForumStats
            {
                EntityID = result.Forum.EntityID
            });
            uow.Execute(new UpdateForumMemberStats
            {
                EntityID = result.Member.EntityID
            });
        }

        protected override void Construct(IUnitOfWork uow, ForumTopicEntity entity)
        {
            base.Construct(uow, entity);

            entity.Member = uow.Execute(new GetForumMember
            {
                ThrowsException = true,
                EntityID = MemberID
            }).Result;
            entity.Forum = uow.Execute(new GetForum
            {
                ThrowsException = true,
                EntityID = ForumID
            }).Result;
            entity.Title = Title.TrimToLen(100);
            entity.Description = StringHelper.EnsureNotNull(Description);
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the forum topic.
    /// </summary>
    public class UpdateForumTopic : UpdateAuditable<ForumTopicEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FORUM");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var member = ctx.ForumMember(uow);
                        if(member == null || !member.Flags.HasFlag(ForumMemberFlags.Active))
                            return false;
                        var topic = uow.Execute(new GetForumTopic
                        {
                            EntityID = EntityID
                        }).Result;
                        if(topic == null)
                            return false;
                        if(topic.Member.EntityID == member.EntityID && topic.Forum.EntityID == ForumID)
                            return true;
                        var srcAcl = topic.Forum.AccessRules;
                        if(!srcAcl.Any(x => member.Account.Roles.Contains(x.Role) &&
                            x.AccessRights >= ForumAccessRights.Moderate))
                        {
                            return false;
                        }
                        var dstForum = uow.Execute(new GetForum
                        {
                            EntityID = ForumID
                        }).Result;
                        if(dstForum == null)
                            return false;
                        var dstAcl = dstForum.AccessRules;
                        return !dstAcl.Any() || dstAcl.Any(x => member.Account.Roles.Contains(x.Role) &&
                            x.AccessRights >= ForumAccessRights.ReadWrite);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the forum identifier.
        /// </summary>
        public int ForumID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        public string Title
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        public string Description
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public ForumTopicFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrEmpty(Title))
                return Err.Of<ForumTopicEntity>().NotValid("Title");
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out ForumTopicEntity result)
        {
            var oldForum = uow.Execute(new GetForumTopic
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result.Forum;

            base.Work(uow, out result);

            uow.Execute(new UpdateForumStats
            {
                EntityID = result.Forum.EntityID
            });
            if(!oldForum.Equals(result.Forum))
            {
                uow.Execute(new UpdateForumStats
                {
                    EntityID = oldForum.EntityID
                });
            }
        }

        protected override void Revise(IUnitOfWork uow, ForumTopicEntity entity)
        {
            base.Revise(uow, entity);

            entity.Forum = uow.Execute(new GetForum
            {
                ThrowsException = true,
                EntityID = ForumID
            }).Result;
            entity.Title = Title.TrimToLen(100);
            entity.Description = StringHelper.EnsureNotNull(Description);
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the forum topic statistics.
    /// </summary>
    public class UpdateForumTopicStats : OperateEntityByID<ForumTopicEntity>
    {
        #region Utilities

        protected override void Work(IUnitOfWork uow, out ForumTopicEntity result)
        {
            var topic = uow.Execute(new GetForumTopic
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;

            uow.Flush();
            uow.SqlQuery("exec fwk_ForumTopics_UpdateStats @TopicID=:TopicID, @UpdatedOn=:UpdatedOn")
                .SetInt32("TopicID", topic.EntityID)
                .SetDateTime("UpdatedOn", DateTime.UtcNow)
                .ExecuteUpdate();
            uow.Flush();
            
            result = uow.Execute(new RefreshEntity<ForumTopicEntity>
            {
                Entity = topic
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the forum topic.
    /// </summary>
    public class DeleteForumTopic : DeleteEntity<ForumTopicEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("FORUM");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var member = ctx.ForumMember(uow);
                        if(member == null || !member.Flags.HasFlag(ForumMemberFlags.Active))
                            return false;
                        var topic = uow.Execute(new GetForumTopic
                        {
                            EntityID = EntityID
                        }).Result;
                        if(topic == null)
                            return false;
                        var acl = topic.Forum.AccessRules;
                        return acl.Any(x => member.Account.Roles.Contains(x.Role) &&
                            x.AccessRights >= ForumAccessRights.Moderate);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow)
        {
            var topic = uow.Execute(new GetForumTopic
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            var forum = topic.Forum;
            var member = topic.Member;

            base.Work(uow);

            uow.Execute(new UpdateForumStats
            {
                EntityID = forum.EntityID
            });
            uow.Execute(new UpdateForumMemberStats
            {
                EntityID = member.EntityID
            });
        }

        protected override void Destruct(IUnitOfWork uow, ForumTopicEntity entity)
        {
            foreach(var i in entity.Posts)
            {
                uow.Execute(new DeleteForumPost
                {
                    EntityID = i.EntityID
                });
            }
            base.Destruct(uow, entity);
        }

        #endregion
    }

    /// <summary>
    /// Gets the forum topic by identifier.
    /// </summary>
    public class GetForumTopic : QueryEntityByID<ForumTopicEntity>,
        IApiOperation
    {
        #region Utilities

        protected override IQueryable<ForumTopicEntity> Build(IUnitOfWork uow, IQueryable<ForumTopicEntity> query)
        {
            query = base.Build(uow, query);
            if(Fetch > FetchLevel.None)
                query = query.Fetch(x => x.Member)
                    .ThenFetch(x => x.Account)
                    .Fetch(x => x.Forum)
                    .Fetch(x => x.LastPost);
            return query;
        }

        #endregion
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over forum topic collection.
    /// </summary>
    public class SearchForumTopics : SearchOperation<ForumTopicEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchForumTopics()
        {
            Term = SearchTerm.Empty;
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                ForumTopicSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the forum identifier.
        /// </summary>
        public int? ForumID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the member identifier.
        /// </summary>
        public int? MemberID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the post count "from".
        /// </summary>
        public int? PostCountFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the post count "to".
        /// </summary>
        public int? PostCountTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public ForumTopicFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        ///   Gets or sets the creation date 'from'.
        /// </summary>
        public DateTime? CreatedFrom
        {
            get;
            set;
        }

        /// <summary>
        ///   Gets or sets the creation date 'to'.
        /// </summary>
        public DateTime? CreatedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public ForumTopicSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<ForumTopicEntity> Build(IUnitOfWork uow, IQueryable<ForumTopicEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Member)
                .ThenFetch(x => x.Account)
                .Fetch(x => x.Forum)
                .Fetch(x => x.LastPost);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            query = query.FilterByDateRange(x => x.CreatedOn, CreatedFrom, CreatedTo);
            query = query.FilterByNumRange(x => x.PostCount, PostCountFrom, PostCountTo);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.Title.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.Title.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.Title.Contains(Term.Keyword) ||
                        x.Description.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.Title.Equals(Term.Keyword));
                    break;
            }
            if(ForumID.HasValue)
                query = query.Where(x => x.Forum.EntityID == ForumID.Value);
            if(MemberID.HasValue)
                query = query.Where(x => x.Member.EntityID == MemberID.Value);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case ForumTopicSortBy.PostCount:
                            query = query.OrderBy(x => x.PostCount, SortDir, i != 0);
                            break;
                        case ForumTopicSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    #endregion
}
